/* Copyright (C) 2010 Luca Piccinelli
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

/* 
 * File:   generator_factory.h
 * Author: picci
 *
 * Created on February 5, 2011, 6:07 PM
 */

#ifndef FACTORY_H
#define	FACTORY_H

#include "prj_definitions.h"
#include "common/utility_data_structures.h"
#include "generators/generators.h"
#include "generators/genericGenerator.h"
#include "exceptions/factory_exceptions.h"
#include "configuration/configuration.h"

#include<map>
#include<list>
#include<string>
#include<sstream>

namespace NAMESPACE{
template<typename BuildedT>
class FactoriesPool;

// ****** factory interface definition *****************************************
template<typename FactorizedT>
class Factory{
public:
    typedef FactorizedT factorized_t;

    virtual ~Factory(){}
    virtual FactorizedT* build() const = 0;
    virtual FactorizedT* build(const Configuration& cfg) const throw(bad_parameter, bad_class) = 0;
    virtual const ConfigurationTemplate& get_configuration_template() const = 0;
};
// -----------------------------------------------------------------------------

// ****** factory abstract class definition ************************************
template<typename FactorizedT>
class SimpleFactory : public Factory<FactorizedT>{
public:
    SimpleFactory() : Factory<FactorizedT>() {}
    SimpleFactory(const SimpleFactory& f) : Factory<FactorizedT>(f){}
    SimpleFactory& operator=(const SimpleFactory& f){
        if (this == &f) return *this;
        Factory<FactorizedT>::operator =(f);
        return *this;
    }
    virtual ~SimpleFactory(){}

    virtual FactorizedT* build() const = 0;
    virtual FactorizedT* build(const Configuration& cfg) const throw(bad_parameter, bad_class) = 0;
    virtual const ConfigurationTemplate& get_configuration_template() const = 0;
};
// -----------------------------------------------------------------------------

// ****** SimpleFactoryImpl class definition ***********************************
template<typename BaseFactorizedT, typename FactorizedT>
class SimpleFactoryImpl : public SimpleFactory<BaseFactorizedT>{
public:
    typedef FactorizedT concrete_t;
    typedef BaseFactorizedT abstract_t;
    
    SimpleFactoryImpl() : SimpleFactory<BaseFactorizedT>() {
        extern FactoriesPool<BaseFactorizedT>* computation_factories_pool;
        computation_factories_pool->add_factory(TypeParseTraits<FactorizedT>::name(), this);
    }
    SimpleFactoryImpl(const SimpleFactoryImpl& f) : SimpleFactory<BaseFactorizedT>(f){}
    SimpleFactoryImpl& operator=(const SimpleFactoryImpl& f){
        if (this == &f) return *this;
        SimpleFactory<BaseFactorizedT>::operator =(f);
        return *this;
    }
    virtual ~SimpleFactoryImpl(){}

    virtual BaseFactorizedT* build() const{
        BaseFactorizedT* builded = new FactorizedT();
        return builded;
    }

    FactorizedT* build(const Configuration& cfg) const throw(bad_parameter, bad_class){
        throw bad_class("Not configurable class");
    }

    const ConfigurationTemplate& get_configuration_template() const {
        throw bad_class("Not configurable class");
    }
};
// -----------------------------------------------------------------------------

// ****** factory configurable class definition ********************************
template<typename FactorizedT>
class ConfigurableFactory : public SimpleFactory<FactorizedT>{
protected:
    ConfigurationTemplate cfg_tmplt;

public:
    virtual ~ConfigurableFactory(){}
    ConfigurableFactory() : SimpleFactory<FactorizedT>(),
                            cfg_tmplt() {}
    ConfigurableFactory(const ConfigurableFactory& f) : SimpleFactory<FactorizedT>(f),
                                                        cfg_tmplt(f.cfg_tmplt) {}
    ConfigurableFactory& operator=(const ConfigurableFactory& f){
        if (this == &f) return *this;
        SimpleFactory<FactorizedT>::operator =(f);
        cfg_tmplt = f.cfg_tmplt;
        return *this;
    }

    virtual FactorizedT* build() const = 0;
    virtual FactorizedT* build(const Configuration& cfg) const throw(bad_parameter) = 0;

    virtual const ConfigurationTemplate& get_configuration_template() const{
        return cfg_tmplt;
    }
};
// -----------------------------------------------------------------------------

// ****** Static FactoriesPool definition ********************************************
template<typename BuildedT>
class FactoriesPool{
private:
    FactoriesPool(){}
    FactoriesPool(const FactoriesPool& b);
    FactoriesPool& operator=(const FactoriesPool& b);

    std::map< std::string, Factory<BuildedT>* > factory_map;
    static FactoriesPool<BuildedT>* singleton_instance;

public:
    typedef BuildedT builded_t;
    ~FactoriesPool(){}

    void add_factory(const std::string& class_name, Factory<BuildedT>* factory){
        Factory<BuildedT>* f = factory_map[class_name];
        if(f) delete f;
        factory_map[class_name] = factory;
    }

    static FactoriesPool<BuildedT>* get_instance(){
        if(!singleton_instance) singleton_instance = new FactoriesPool<BuildedT>();
        return singleton_instance;
    }
    
    static void free_instance(){
        if(singleton_instance) delete singleton_instance;
    }

    const ConfigurationTemplate& get_configuration_template(const std::string& class_name) throw(bad_class){
        const Factory<BuildedT>* f = factory_map[class_name];
        if(!f){
            std::stringstream ss;
            ss << "No such class: " << class_name;
            throw bad_class(ss.str());
        }

        return f->get_configuration_template();
    }

    BuildedT* build( const std::string& class_name,
                     const Configuration& conf = Configuration() ) throw(bad_parameter, bad_class){
        const Factory<BuildedT>* f = factory_map[class_name];
        if(!f){
            std::stringstream ss;
            ss << "No such class: " << class_name;
            throw bad_class(ss.str());
        }

        BuildedT* g;
        if(conf.get_template().parameters_num() != 0){
            g = f->build(conf);
        }else {
            g = f->build();
        }
        return g;
    }

    void list_all_classes( std::list<std::string>& out_list ) {
        typename std::map< std::string, Factory<BuildedT>* >::const_iterator it     = factory_map.begin(),
                                                                             end_it = factory_map.end();

        out_list.clear();
        for(; it != end_it; it++){
            if(it->second) out_list.push_back(it->first);
        }
    }
};

template<typename BuildedT>
FactoriesPool<BuildedT>* FactoriesPool<BuildedT>::singleton_instance = 0;
// -----------------------------------------------------------------------------

// ****** GenericGeneratorFactory implementation ***************************
template<typename T, typename Base>
class GenericGeneratorFactory : public ConfigurableFactory<Generator>{
public:
    typedef T generated_t;
    typedef Base base_t;
    
    GenericGeneratorFactory() : ConfigurableFactory<Generator>(){
        extern FactoriesPool<Generator>* generator_factories_pool;
        generator_factories_pool->add_factory(TypeParseTraits< GenericGenerator<T, Base> >::name(), this);
        IOKey k0;
        k0.set_key(PARAMETER_NS, "iokey"); k0.setType(TypeParseTraits<IOKey>::name());
        cfg_tmplt.add(k0);
    }

    Generator* build() const {
        return new GenericGenerator<T, Base>();
    }

    Generator* build(const Configuration& cfg) const throw(bad_parameter){
        IOKey k = cfg.get_parameter<IOKey>(cfg_tmplt.param_at(0));
        return new GenericGenerator<T, Base>(k);
    }
};
// -----------------------------------------------------------------------------

}

#endif	/* FACTORY_H */

